From fc27940fe9939f99aeb988d021c7edfa54460123 Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20H=C3=A4rdeman?= Date: Fri, 10 Oct 2025 09:38:38 +0200 Subject: [PATCH] dhcpv6: support a configurable DUID MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Allow the use of a (stable) globally configured DUID as the server identifier. Currently, odhcpd generates a per-interface DUID-LL, meaning that the DUID is not stable across interfaces and will change if/when hardware changes. Supporting a stable DUID brings odhcpd's behaviour in line with RFC8415, §11: ...The DUID is designed to be unique across all DHCP clients and servers, and stable for any specific client or server. That is, the DUID used by a client or server SHOULD NOT change over time if at all possible; for example, a device's DUID should not change as a result of a change in the device's network hardware or changes to virtual interfaces... Signed-off-by: David Härdeman Link: https://github.com/openwrt/odhcpd/pull/274 Signed-off-by: Álvaro Fernández Rojas --- README.md | 8 +++++++ src/config.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++-- src/dhcpv6-ia.c | 13 ++++++++---- src/dhcpv6.c | 13 ++++++++---- src/odhcpd.h | 20 ++++++++++++++---- 5 files changed, 96 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 65787d7..5d63cd4 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,14 @@ and may also receive information from ubus | url |string | yes | e.g. `tftp://[fd11::1]/pxe.efi` | | arch |integer| no | the arch code. `07` is EFI. If not present, this boot6 will be the default. | +odhcpd also uses the UCI configuration file `/etc/config/network` for configuration +of the following options: + +### Section of type globals +| Option | Type |Required|Description | +| :---------------- | :---- | :---- | :---------- | +| dhcp_default_duid |string | no | The DUID to use to identify the DHCPv6 server to clients. | + ### System variables for Timezone options (uci system.system) | Option | Type |Required|Description | diff --git a/src/config.c b/src/config.c index 7bff307..654c329 100644 --- a/src/config.c +++ b/src/config.c @@ -45,6 +45,8 @@ struct config config = { .log_level = LOG_WARNING, .log_level_cmdline = false, .log_syslog = true, + .default_duid = { 0 }, + .default_duid_len = 0, }; struct sys_conf sys_conf = { @@ -260,6 +262,20 @@ const struct uci_blob_param_list system_attr_list = { .params = system_attrs, }; +enum { + GLOBAL_ATTR_DUID, + GLOBAL_ATTR_MAX +}; + +static const struct blobmsg_policy global_attrs[GLOBAL_ATTR_MAX] = { + [GLOBAL_ATTR_DUID] = { .name = "dhcp_default_duid", .type = BLOBMSG_TYPE_STRING }, +}; + +const struct uci_blob_param_list global_attr_list = { + .n_params = GLOBAL_ATTR_MAX, + .params = global_attrs, +}; + static const struct { const char *name; uint8_t flag; } ra_flags[] = { { .name = "managed-config", .flag = ND_RA_FLAG_MANAGED }, { .name = "other-config", .flag = ND_RA_FLAG_OTHER }, @@ -415,6 +431,26 @@ static int parse_ra_flags(uint8_t *flags, struct blob_attr *attr) return 0; } +static void set_global_config(struct uci_section *s) +{ + struct blob_attr *tb[GLOBAL_ATTR_MAX], *c; + + blob_buf_init(&b, 0); + uci_to_blob(&b, s, &global_attr_list); + blobmsg_parse(global_attrs, GLOBAL_ATTR_MAX, tb, blob_data(b.head), blob_len(b.head)); + + if ((c = tb[GLOBAL_ATTR_DUID])) { + size_t len = blobmsg_data_len(c) / 2; + + config.default_duid_len = 0; + if (len >= DUID_MIN_LEN && len <= DUID_MAX_LEN) { + ssize_t r = odhcpd_unhexlify(config.default_duid, len, blobmsg_get_string(c)); + if (r >= DUID_MIN_LEN) + config.default_duid_len = r; + } + } +} + static void set_config(struct uci_section *s) { struct blob_attr *tb[ODHCPD_ATTR_MAX], *c; @@ -2227,6 +2263,7 @@ void odhcpd_reload(void) struct interface *master = NULL, *i, *tmp; char *uci_dhcp_path = "dhcp"; char *uci_system_path = "system"; + char *uci_network_path = "network"; if (!uci) return; @@ -2238,17 +2275,32 @@ void odhcpd_reload(void) sprintf(uci_dhcp_path, "%s/dhcp", config.uci_cfgdir); uci_system_path = alloca(dlen + sizeof("/system")); sprintf(uci_system_path, "%s/system", config.uci_cfgdir); + uci_network_path = alloca(dlen + sizeof("/network")); + sprintf(uci_network_path, "%s/network", config.uci_cfgdir); } vlist_update(&leases); avl_for_each_element(&interfaces, i, avl) clean_interface(i); + struct uci_package *network = NULL; + if (!uci_load(uci, uci_network_path, &network)) { + struct uci_element *e; + + /* 0. Global settings */ + uci_foreach_element(&network->sections, e) { + struct uci_section *s = uci_to_section(e); + if (!strcmp(s->type, "globals")) + set_global_config(s); + } + } + uci_unload(uci, network); + struct uci_package *dhcp = NULL; if (!uci_load(uci, uci_dhcp_path, &dhcp)) { struct uci_element *e; - /* 1. Global settings */ + /* 1. General odhcpd settings */ uci_foreach_element(&dhcp->sections, e) { struct uci_section *s = uci_to_section(e); if (!strcmp(s->type, "odhcpd")) @@ -2284,7 +2336,7 @@ void odhcpd_reload(void) if (config.enable_tz && !uci_load(uci, uci_system_path, &system)) { struct uci_element *e; - /* 1. System settings */ + /* 5. System settings */ uci_foreach_element(&system->sections, e) { struct uci_section *s = uci_to_section(e); if (!strcmp(s->type, "system")) diff --git a/src/dhcpv6-ia.c b/src/dhcpv6-ia.c index 2948607..b2efd4c 100644 --- a/src/dhcpv6-ia.c +++ b/src/dhcpv6-ia.c @@ -191,10 +191,15 @@ static int send_reconf(struct dhcp_assignment *assign) .key = { 0 }, }; - uint8_t duid_ll_hdr[] = { 0x00, 0x03, 0x00, 0x01 }; - memcpy(serverid.data, duid_ll_hdr, sizeof(duid_ll_hdr)); - odhcpd_get_mac(iface, &serverid.data[sizeof(duid_ll_hdr)]); - serverid.len = htons(sizeof(duid_ll_hdr) + ETH_ALEN); + if (config.default_duid_len > 0) { + memcpy(serverid.data, config.default_duid, config.default_duid_len); + serverid.len = htons(config.default_duid_len); + } else { + uint16_t duid_ll_hdr[] = { htons(DUID_TYPE_LL), htons(ARPHRD_ETHER) }; + memcpy(serverid.data, duid_ll_hdr, sizeof(duid_ll_hdr)); + odhcpd_get_mac(iface, &serverid.data[sizeof(duid_ll_hdr)]); + serverid.len = htons(sizeof(duid_ll_hdr) + ETH_ALEN); + } memcpy(clientid.data, assign->clid_data, assign->clid_len); diff --git a/src/dhcpv6.c b/src/dhcpv6.c index 72e1855..ab55ff6 100644 --- a/src/dhcpv6.c +++ b/src/dhcpv6.c @@ -378,10 +378,15 @@ static void handle_client_request(void *addr, void *data, size_t len, .serverid_buf = { 0 }, }; - uint8_t duid_ll_hdr[] = { 0x00, 0x03, 0x00, 0x01 }; - memcpy(dest.serverid_buf, duid_ll_hdr, sizeof(duid_ll_hdr)); - odhcpd_get_mac(iface, &dest.serverid_buf[sizeof(duid_ll_hdr)]); - dest.serverid_length = htons(sizeof(duid_ll_hdr) + ETH_ALEN); + if (config.default_duid_len > 0) { + memcpy(dest.serverid_buf, config.default_duid, config.default_duid_len); + dest.serverid_length = htons(config.default_duid_len); + } else { + uint16_t duid_ll_hdr[] = { htons(DUID_TYPE_LL), htons(ARPHRD_ETHER) }; + memcpy(dest.serverid_buf, duid_ll_hdr, sizeof(duid_ll_hdr)); + odhcpd_get_mac(iface, &dest.serverid_buf[sizeof(duid_ll_hdr)]); + dest.serverid_length = htons(sizeof(duid_ll_hdr) + ETH_ALEN); + } struct _packed { uint16_t type; diff --git a/src/odhcpd.h b/src/odhcpd.h index f2e2e74..55107e5 100644 --- a/src/odhcpd.h +++ b/src/odhcpd.h @@ -183,6 +183,19 @@ enum odhcpd_assignment_flags { OAF_DHCPV6_PD = (1 << 6), }; +/* 2-byte type + 128-byte DUID, RFC8415, §11.1 */ +#define DUID_MAX_LEN 130 +/* In theory, 2 (type only), or 7 (DUID-EN + 1-byte data), but be reasonable */ +#define DUID_MIN_LEN 10 +#define DUID_HEXSTRLEN (DUID_MAX_LEN * 2 + 1) + +enum duid_type { + DUID_TYPE_LLT = 1, + DUID_TYPE_EN = 2, + DUID_TYPE_LL = 3, + DUID_TYPE_UUID = 4, +}; + struct config { bool legacy; bool enable_tz; @@ -198,6 +211,9 @@ struct config { int log_level; bool log_level_cmdline; bool log_syslog; + + uint8_t default_duid[DUID_MAX_LEN]; + size_t default_duid_len; }; struct sys_conf { @@ -207,10 +223,6 @@ struct sys_conf { size_t tzdb_tz_len; }; -/* 2-byte type + 128-byte DUID, RFC8415, §11.1 */ -#define DUID_MAX_LEN 130 -#define DUID_HEXSTRLEN (DUID_MAX_LEN * 2 + 1) - struct duid { uint8_t len; uint8_t id[DUID_MAX_LEN]; -- 2.30.2